Skip to content

fix(ai-openrouter): preserve disabled reasoning requests - #1013

Merged
AlemTuzlak merged 5 commits into
TanStack:mainfrom
kinKingen:fix/openrouter-empty-reasoning
Aug 21, 2026
Merged

fix(ai-openrouter): preserve disabled reasoning requests#1013
AlemTuzlak merged 5 commits into
TanStack:mainfrom
kinKingen:fix/openrouter-empty-reasoning

Conversation

@kinKingen

@kinKingen kinKingen commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Fixes #1006.

  • Preserve explicit reasoning: { enabled: false } requests by normalizing them to the SDK-supported reasoning: { effort: 'none' }.
  • Omit empty reasoning objects instead of sending reasoning: {}.
  • Add unit coverage and a real SDK-to-HTTP wire regression test.
  • Add a patch changeset for @tanstack/ai-openrouter.

Root cause

The adapter accepted OpenRouter's reasoning.enabled option and passed it to @openrouter/sdk@0.13.20. The generated SDK serializer does not include the enabled field, so { enabled: false } became an empty reasoning: {} object on the wire. OpenRouter then applied its default reasoning behavior, effectively re-enabling what the caller explicitly disabled.

The fix removes the unsupported field before serialization, maps explicit false to effort: 'none', and omits the object when no supported fields remain. The SDK version stays unchanged.

Validation

  • OpenRouter adapter tests: 146 passed
  • pnpm test:pr
  • Package type checks, build, and oxlint
  • E2E package type check
  • Full E2E suite: 393 passed, 1 skipped; 3 flaky cases passed on retry
  • New wire regression verifies the serialized request contains reasoning: { effort: 'none' }

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

AI assistance disclosure

This contribution was developed with AI assistance. I reviewed the implementation, tests, and validation results and take responsibility for the submitted changes.

Summary by CodeRabbit

  • Bug Fixes
    • OpenRouter reasoning opt-out is now honored reliably by converting reasoning: { enabled: false } to the SDK-supported wire format (reasoning: { effort: 'none' }).
    • Empty reasoning options (reasoning: {}) are no longer included in outbound request payloads.
  • Tests
    • Added unit and end-to-end regression coverage for both “disabled” and “empty” reasoning wire-format scenarios, with updated fixtures.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tombeckenham, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1df7260d-7fed-4e27-bda5-c7d3fb239f4b

📥 Commits

Reviewing files that changed from the base of the PR and between f7c67a8 and 75224ca.

📒 Files selected for processing (8)
  • .changeset/openrouter-disable-reasoning.md
  • packages/ai-openrouter/src/adapters/text.ts
  • packages/ai-openrouter/src/text/text-provider-options.ts
  • packages/ai-openrouter/tests/openrouter-adapter.test.ts
  • testing/e2e/fixtures/reasoning/openrouter-disable.json
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.openrouter-reasoning-wire.ts
  • testing/e2e/tests/openrouter-reasoning-wire.spec.ts
📝 Walkthrough

Walkthrough

The OpenRouter adapter accepts reasoning: { enabled: false }, converts it to { effort: 'none' }, omits empty reasoning objects, and validates both wire formats through unit and end-to-end tests.

Changes

OpenRouter reasoning wire format

Layer / File(s) Summary
Reasoning contract and request normalization
packages/ai-openrouter/src/text/text-provider-options.ts, packages/ai-openrouter/src/adapters/text.ts
Reasoning options support enabled: false, normalize it to effort: 'none', and omit empty reasoning values.
Adapter regression coverage
packages/ai-openrouter/tests/openrouter-adapter.test.ts, .changeset/openrouter-disable-reasoning.md
Unit tests cover disabled and empty reasoning serialization, and the changeset records the patch.
End-to-end wire validation
testing/e2e/src/routes/api.openrouter-reasoning-wire.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/tests/openrouter-reasoning-wire.spec.ts, testing/e2e/fixtures/reasoning/openrouter-disable.json
A mock-backed route exercises disabled and empty reasoning scenarios, while e2e tests verify the captured request bodies.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Playwright
  participant ReasoningWireRoute
  participant OpenRouterAdapter
  participant AimockJournal
  Playwright->>ReasoningWireRoute: POST scenario
  ReasoningWireRoute->>OpenRouterAdapter: Stream chat request
  OpenRouterAdapter->>AimockJournal: Send normalized or omitted reasoning
  AimockJournal-->>ReasoningWireRoute: Return mock stream
  ReasoningWireRoute-->>Playwright: Return success
  Playwright->>AimockJournal: Inspect captured request
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: preserving disabled OpenRouter reasoning requests.
Description check ✅ Passed The description follows the template and includes changes, checklist, release impact, and validation details.
Linked Issues check ✅ Passed The PR addresses #1006 by mapping enabled:false to effort:none and omitting empty reasoning objects, with matching tests.
Out of Scope Changes check ✅ Passed The added tests, fixture, and generated route updates all support the OpenRouter reasoning fix and appear in scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-openrouter/tests/openrouter-adapter.test.ts`:
- Around line 1649-1671: Extend the mock-backed E2E route/spec in testing/e2e/
to send a request with reasoning: {} and verify the outbound wire request omits
the reasoning field. Reuse the existing E2E request path and assertions for
reasoning serialization, adding coverage for both the route-level payload and
serialized request if applicable.
- Around line 1625-1671: Move the two reasoning normalization tests from the
package tests directory into a *.test.ts file alongside the adapter source
covered by createAdapter and chat. Preserve both test cases and their assertions
unchanged, updating only imports or references required by the new location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cfb8dca-6cb0-40da-ad59-4a30ec2479d3

📥 Commits

Reviewing files that changed from the base of the PR and between 1120f0f and 4de7723.

📒 Files selected for processing (8)
  • .changeset/openrouter-disable-reasoning.md
  • packages/ai-openrouter/src/adapters/text.ts
  • packages/ai-openrouter/src/text/text-provider-options.ts
  • packages/ai-openrouter/tests/openrouter-adapter.test.ts
  • testing/e2e/fixtures/reasoning/openrouter-disable.json
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.openrouter-reasoning-wire.ts
  • testing/e2e/tests/openrouter-reasoning-wire.spec.ts

Comment thread packages/ai-openrouter/tests/openrouter-adapter.test.ts
Comment thread packages/ai-openrouter/tests/openrouter-adapter.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@testing/e2e/tests/openrouter-reasoning-wire.spec.ts`:
- Around line 27-35: Update the captured journal entry assertion in the scenario
check to first require that captured is defined and that its body is an object,
before validating the reasoning property. Keep the existing enabled and disabled
reasoning expectations unchanged after these presence assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dbbdcb27-8776-4677-b8dd-46401472f2ab

📥 Commits

Reviewing files that changed from the base of the PR and between 4de7723 and 20cb6a7.

📒 Files selected for processing (3)
  • testing/e2e/fixtures/reasoning/openrouter-disable.json
  • testing/e2e/src/routes/api.openrouter-reasoning-wire.ts
  • testing/e2e/tests/openrouter-reasoning-wire.spec.ts

Comment thread testing/e2e/tests/openrouter-reasoning-wire.spec.ts
@tombeckenham
tombeckenham force-pushed the fix/openrouter-empty-reasoning branch from 79b06b0 to 8b86c53 Compare August 10, 2026 09:09
@nx-cloud

nx-cloud Bot commented Aug 10, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 75224ca

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 8s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-21 10:35:59 UTC

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR, @kinKingen! 🙌 @AlemTuzlak will take a look.

Automated pre-review checks

  • ⚠️ CI failing — worth a look before review
  • ✅ No merge conflicts
  • ✅ Changeset present
  • ✅ E2E test changes included

Automated triage — a human review follows.

@github-actions github-actions Bot added the waiting-on: author Waiting for the author to respond or update label Aug 13, 2026
@tombeckenham
tombeckenham force-pushed the fix/openrouter-empty-reasoning branch 2 times, most recently from 5caab67 to 387cf83 Compare August 20, 2026 10:52
@tombeckenham
tombeckenham force-pushed the fix/openrouter-empty-reasoning branch from 387cf83 to 75224ca Compare August 21, 2026 03:29

@AlemTuzlak AlemTuzlak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I treated this diff as invasive and checked whether each piece earns its keep.

The bug is real, and this mapping fixes it.

@openrouter/sdk@0.13.20 ChatRequestReasoning$outboundSchema still turns { enabled: false } into {}. { effort: 'none' } survives. That matches #1006.

I ran the two new unit tests both ways:

  • without normalizeReasoningOptions, they fail ({ enabled: false } is passed through; reasoning: {} is sent)
  • with the helper, they pass, including ChatRequest$outboundSchema.parse(...)

Keep

  • normalizeReasoningOptions: mapping enabled: false to effort: 'none' is what actually turns reasoning off. If we only drop enabled and omit the empty object, OpenRouter uses default reasoning.
  • enabled?: false on ReasoningOptions: callers can pass the documented OpenRouter flag.
  • the two unit tests plus ChatRequest$outboundSchema.parse: that is the serializer that caused the bug.
  • the changeset.
  • the as ExternalTextProviderOptions cast: I tried to drop it. tsc fails because some per-model option unions omit reasoning.

Keep, but it is extra

  • the dedicated E2E route, fixture, spec, and routeTree.gen.ts update. The unit tests already parse the SDK outbound schema. The E2E hits HTTP after HTTPClient. Repo policy wants E2E for behavior changes, so I would leave it.

Do not do

  • CodeRabbit's request to move unit tests next to src/. CONTRIBUTING.md says packages/<pkg>/tests/.

Nits, not blockers

  • The enabled JSDoc restates the SDK bug. One line is enough: the adapter sends this as effort: 'none'.
  • docs/adapters/openrouter.md has no reasoning section. A short modelOptions.reasoning example would help callers who copy enabled: false from OpenRouter docs.

CodeRabbit threads are already resolved. Preview CI is a pkg.pr.new 404 on the fork, not this change.

@pkg-pr-new

pkg-pr-new Bot commented Aug 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@1013

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@1013

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@1013

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@1013

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@1013

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@1013

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@1013

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@1013

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@1013

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@1013

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@1013

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@1013

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@1013

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@1013

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@1013

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@1013

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@1013

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@1013

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@1013

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@1013

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@1013

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@1013

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@1013

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@1013

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@1013

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@1013

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@1013

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@1013

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@1013

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@1013

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@1013

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@1013

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@1013

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@1013

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@1013

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@1013

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@1013

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@1013

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@1013

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@1013

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@1013

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@1013

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@1013

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@1013

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@1013

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@1013

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@1013

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@1013

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@1013

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@1013

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@1013

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@1013

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@1013

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@1013

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@1013

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@1013

commit: 75224ca

@AlemTuzlak
AlemTuzlak merged commit 4338500 into TanStack:main Aug 21, 2026
10 of 11 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: author Waiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenRouter: reasoning: { enabled: false } is stripped to reasoning: {}, which re-enables reasoning

2 participants